﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Configuration;
using System.Data.Linq;
using System.Diagnostics;
using System.Linq;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Threading;
using PersonaLibrary.Data;
using PersonaLibrary.Search;

namespace PersonaLibrary.ViewModel.MainWindowViewModel
{
	public class MainWindowViewModel : BaseViewModel<View.Host.MainWindow>
	{
		#region Private members

		private string _alphabet = "абвгдеёжзийклмнопрстуфхцчшщыэюя".ToUpper();
		private CustomProperties.PropertiesItem _currentProperties;
		private Dictionary<string, string> _orderProperties;
		private Func<BaseEntity, bool> _filter;
		private ObservableCollection<BaseEntity> _observableCollection = new ObservableCollection<BaseEntity>();
		private ListCollectionViewWrapper _collection;
		private IEnumerable<BaseEntity> _table;
		private Type _itemType;
		private LinqToDbDataContext _context;
		private bool _inSearched;
		private IDictionary<string, KeyValuePair<int, IEnumerable>> _searchResult;
		private bool _isBusy;
		private ObservableCollection<Filter> _historyCache = new ObservableCollection<Filter>();
		private ItemsSelectorViewModel _itemsSelector;

		#endregion

		#region Private commands

		private RelayCommand _simplySearchCommand;
		private RelayCommand _showSimplySearchResultC;
		private RelayCommand _resetCommand;
		private RelayCommand _orderCommand;
		private RelayCommand _showSelectorCommand;
		private RelayCommand _showCollectionFilter;
		private RelayCommand _refreshItem;

		#endregion

		#region Public commands

		public RelayCommand RefreshItemCommand
		{
			get
			{
				if(_refreshItem == null)
					_refreshItem = new RelayCommand(x => RefreshItem(x), x => x is BaseEntity);

				return _refreshItem;
			}
		}

		public RelayCommand ResetCommand
		{
			get
			{
				if (_resetCommand == null) _resetCommand = new RelayCommand(param => ResetCurrentOperation(), param => true);
				return _resetCommand;
			}
		}

		public RelayCommand ShowSimplySearchResultC
		{
			get
			{
				if (_showSimplySearchResultC == null) _showSimplySearchResultC = new RelayCommand(param => ShowSearchResult(param.ToString()), param => true);
				return _showSimplySearchResultC;
			}
		}

		public RelayCommand SimplySearchCommand
		{
			get
			{
				if(_simplySearchCommand == null) _simplySearchCommand = new RelayCommand(param => SimplySearch(param.ToString()), param => true);
				return _simplySearchCommand;
			}
		}

		public RelayCommand OrderCommand
		{
			get
			{
				if(_orderCommand == null)
					_orderCommand = new RelayCommand(x => Order(x.ToString()), x => true);

				return _orderCommand;
			}
		}

		public RelayCommand ShowSelectorCommand
		{
			get
			{
				if(_showSelectorCommand == null)
					_showSelectorCommand = new RelayCommand(x => ShowSelector(), x => true);

				return _showSelectorCommand;
			}
		}

		public RelayCommand ShowCollectionFilter
		{
			get
			{
				if (_showCollectionFilter == null)
					_showCollectionFilter = new RelayCommand(x => _itemsSelector.View.Show(), x => true);
				return _showCollectionFilter;
			}
		}

		#endregion

		public MainWindowViewModel()
		{
			LinqToDbDataContext.ItemAdded += LinqToDbDataContext_ItemAdded;
			LinqToDbDataContext.ItemDeleted += LinqToDbDataContext_ItemDeleted;
			LinqToDbDataContext.ItemUpdated += LinqToDbDataContext_ItemUpdated;

			CurrentApplication.FilterChanged += MainWindowViewModel_FilterChanged;

			_itemsSelector = new ItemsSelectorViewModel();
			_itemsSelector.PropertyChanged += delegate(object sender, PropertyChangedEventArgs e)
			                                  	{
													if(e.PropertyName == "Filter")
			                                  			RisePropertyChanged("CollectionFilter");
			                                  	};
		}

		private void MainWindowViewModel_FilterChanged()
		{
			_context = new LinqToDbDataContext();
			_itemsSelector.ResetFilter();

			Action clear = _observableCollection.Clear;
			Dispatcher.Invoke(clear);
			AlphabetFilter alpFilter = CurrentApplication.Filter.ContainseFilter(typeof (AlphabetFilter).Name) as AlphabetFilter;
			
			if (alpFilter != null)
				CurrentProperties = alpFilter.Alphabet;
			RisePropertyChanged("CurrentProperties");

			_itemType = CurrentApplication.Filter.FilterType;

			if (_itemType.Name == "Employee")
			{
				_collection.SortDescriptions.Clear();
				_collection.SortDescriptions.Add(new SortDescription("Surname", ListSortDirection.Ascending));
				_collection.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
				_collection.SortDescriptions.Add(new SortDescription("Patronymic", ListSortDirection.Ascending));
			}

			_table = _context.GetTable(CurrentApplication.Filter.FilterType).OfType<BaseEntity>();

			_filter = CurrentApplication.Filter.ToFunc();

			CollectionName = CurrentApplication.Filter.DisplayName;
			RisePropertyChanged("CollectionName");

			Action search = Search;
			search.BeginInvoke(SearchCallback, null);
		}

		private void Search()
		{
			IsBusy = true;
			Action<BaseEntity> add = _observableCollection.Add;
			foreach (BaseEntity baseEntity in _table.Where(_filter))
			{
				if (IsBusy)
				{
					Dispatcher.Invoke(add, DispatcherPriority.ContextIdle, baseEntity);
					baseEntity.LoadPartialFields();
				}
				else
					break;
			}
		}

		private void RefreshItem(object item)
		{
			_context.Refresh(RefreshMode.OverwriteCurrentValues, item);
			_collection.Refresh();
		}

		private void SearchCallback(object arg)
		{
			RisePropertyChanged("Collection");
			RisePropertyChanged("CurrentPack");

			if (_historyCache.Count == 5)
			{
				Action<int> removeAt = _historyCache.RemoveAt;
				Dispatcher.Invoke(removeAt, DispatcherPriority.ContextIdle, 0);
			}

			Action<Filter> addHistory = _historyCache.Add;
			Dispatcher.Invoke(addHistory, DispatcherPriority.ContextIdle, CurrentApplication.Filter);

			IsBusy = false;
			_itemsSelector.SetCollection(_observableCollection);

			FillOrderProperties();

			GC.Collect();
		}

		private void LinqToDbDataContext_ItemUpdated(BaseEntity obj)
		{
			List<BaseEntity> items = _observableCollection.Where(x => x.Id == obj.Id).ToList();

			foreach (var item in items)
			{
				int index = _observableCollection.IndexOf(item);
				_context.Refresh(RefreshMode.OverwriteCurrentValues, _observableCollection[index]);
				
				_collection.Refresh();
			}

			RisePropertyChanged("Collection");
		}

		private void LinqToDbDataContext_ItemDeleted(BaseEntity obj)
		{
			if (obj.GetType() == _itemType && _observableCollection.Any(x => x.Id == obj.Id))
				_observableCollection.Remove(_observableCollection.Single(x => x.Id == obj.Id));

			if (obj is EmployeeNoteLink && _itemType == typeof(Employee))
			{
				EmployeeNoteLink employeeNoteLink = (EmployeeNoteLink) obj;
				Employee employee = _observableCollection.FirstOrDefault(x => x.Id == employeeNoteLink.EmployeeId) as Employee;

				if (employee != null)
				{
					employee.EmployeeNoteLinks.Remove(employee.EmployeeNoteLinks.FirstOrDefault(x => x.Id == employeeNoteLink.Id));
					employee.LoadPartialFields();
					RefreshItem(employee);
				}
			}
		}

		private void LinqToDbDataContext_ItemAdded(BaseEntity obj)
		{
			if (obj.GetType() == _itemType && _filter.Invoke(obj) && !_observableCollection.Contains(obj))
				_observableCollection.Add(obj);

			if(obj is EmployeeNoteLink && _itemType == typeof(Employee))
			{
				EmployeeNoteLink employeeNoteLink = (EmployeeNoteLink) obj;
				Employee employee = _observableCollection.FirstOrDefault(x => x.Id == employeeNoteLink.EmployeeId) as Employee;

				if (employee != null)
				{
					employee.EmployeeNoteLinks.Add(employeeNoteLink);
					employee.LoadPartialFields();
					RefreshItem(employee);
				}
			}
		}

		public ListCollectionViewWrapper Collection
		{
			get
			{
				if(_collection == null)
					_collection = new ListCollectionViewWrapper(_observableCollection);
				return _collection;
			}
		}

		public ObservableCollection<Filter> Filters
		{
			get { return _historyCache; }
		}

		public Predicate<object> CollectionFilter
		{
			get
			{
				return _itemsSelector.Filter;
			}
		}

		public RelayCommand GoToFilterCommand
		{
			get
			{
				return CurrentApplication.GoToFilterCommand;
			}
		}

		public string CollectionName
		{
			private set; get;
		}

		public bool IsBusy
		{
			get { return _isBusy; }
			private set { _isBusy = value; RisePropertyChanged("IsBusy");}
		}

		public Commands.CommandsForTotalItems.ICommandsPack CurrentPack
		{
			get { return Commands.CommandsForTotalItems.CommandsPackStrategy.Interop[_itemType]; }
		}

		public IDictionary<string, KeyValuePair<int, IEnumerable>> SearchResult
		{
			get { return _searchResult; }
		}
		
		public char[] Alphabet
		{
			get
			{
				return _alphabet.ToArray();
			}
		}

		public CustomProperties.PropertiesItem CurrentProperties
		{
			get
			{
				return _currentProperties;
			}
			private set 
			{
				_currentProperties = value;
				RisePropertyChanged("CurrentProperties");
			}
		}

		public Dictionary<string, string> OrderProperties
		{
			get
			{
				return _orderProperties;
			}
			private set
			{
				_orderProperties = value;
				RisePropertyChanged("OrderProperties");
			}
		}

		public bool InSearched
		{ 
			private set
			{
				_inSearched = value; 
				RisePropertyChanged("InSearched");
			}
			get
			{
				return _inSearched;
			}
		}

		private void ResetCurrentOperation()
		{
			_isBusy = false;
			InSearched = false;
			//this.Dispatcher.Thread.Abort();
		}

		private void ShowSearchResult(string resultName)
		{
			InSearched = false;

			_observableCollection.Clear();
			foreach (BaseEntity entity in _searchResult[resultName].Value)
				_observableCollection.Add(entity);

			_itemType = _observableCollection.GetType().GetGenericArguments()[0];
			RisePropertyChanged("Collection");
			GC.Collect();
			FillOrderProperties();
		}

		private void SimplySearch(string arg)
		{
			IsBusy = true;
			InSearched = true;
			Task searchTask = new Task
				(
					() =>
						{
							SimplySearch search = new SimplySearch(arg);
							_searchResult = search.SearchResult;
							RisePropertyChanged("SearchResult");
							IsBusy = false;
						}
				);
			searchTask.Start();
		}
		
		private void Order(string property)
		{
			_collection.SortDescriptions.Clear();
			Stopwatch sortTimer = Stopwatch.StartNew();
			if (property != "MaxOrganizationRank")
				_collection.SortDescriptions.Add(new SortDescription(property, ListSortDirection.Ascending));
			else
				_collection.SortDescriptions.Add(new SortDescription(property, ListSortDirection.Descending));
			sortTimer.Stop();
			Debug.WriteLine("Sort time " + sortTimer.Elapsed);
		}

		private void FillOrderProperties()
		{
			Dictionary<string, string> result = new Dictionary<string, string>();

			List<Metadata.Point> points = Metadata.Instance[_itemType.Name];
			if(points != null)
			{
				foreach (Metadata.Point point in points.Where(x => x.IsOrder))
				{
					result.Add(point.Name, point.RuName);
				}
			}

			OrderProperties = result;
		}

		private void ShowSelector()
		{
			if(_itemsSelector.Opened)
				_itemsSelector.View.Hide();
			else
				_itemsSelector.View.Show();
		}
	}
}
